package com.jl.card.parse;

import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.annotations.JSConstructor;
import org.mozilla.javascript.annotations.JSFunction;

//import com.jl.process.js.ByteArray;
import com.jl.process.util.GPError;
import com.jl.util.HexUtil;

public class ATR extends ScriptableObject {

	private final static String clazzName = "ATR";
	private final static int curr[] = { 25, 50, 100, 200 };
	private final static String[] prot = { "T=0", "T=1", "...", "...", "...", "...",
			"...", "...", "...", "...", "...", "...", "...", "...", "T14",
			"GLO" };
	private final static String[] sprot = { "SDAP", "3WBP", "2WBP" };
	private final static String[] fi = { " 372/ 4", " 372/ 5", " 558/ 6", " 744/ 8",
			"1116/12", "1488/16", "1860/20", " RFU   ", " RFU   ", " 512/ 5",
			"768/7,5", "1024/10", "1536/15", "2048/20", " RFU   ", " RFU   " };
	private final static String[] di = { "RFU", "  1", "  2", "  4", "  8", " 16",
			" 32", "RFU", " 12", " 20", "RFU", "RFU", "RFU", "RFU", "RFU",
			"RFU" };

	private final static String[] xi = { "No Stop", "State L", "State H", "No Pref" };
	private final static String[] ui = { "N/A    ", "A only ", "B only ", "A and B",
			"C only ", "C and A", "B and C", "A, B, C" };

	private byte[] atr = null;

	public ATR() {
	}
	
	@JSConstructor
	public ATR(String hex) {
		atr = HexUtil.parseHexToByteArray(hex);

		if ((atr[0] == 0x3B) || (atr[0] == 0x3F)) {
			this.put("formatByte", this, new Integer(atr[1] & 0xFF));

			int hbc = atr[1] & 0x0F;
			int i = 1;
			boolean more = true;
			while (more) {
				more = (atr[i] & 0x80) == 0x80;
				i += ((atr[i] & 0x10) >> 4) + ((atr[i] & 0x20) >> 5)
						+ ((atr[i] & 0x40) >> 6) + ((atr[i] & 0x80) >> 7);
			}
			i++;

			byte[] ib = new byte[i - 2];
			System.arraycopy(atr, 2, ib, 0, i - 2);
			this.put("interfaceBytes", this, ib);

			byte[] hb = new byte[hbc];
			System.arraycopy(atr, i, hb, 0, hbc);
			this.put("historicalBytes", this, hb);
			i += hbc;

			if (atr.length > i) {
				if (atr.length - i == 1) {
					this.put("tckByte", this, new Integer(atr[i]));
				} else {
					this.put("tckByte", this, new Integer(
							(atr[i] & 0xFF) << 8 + (atr[i + 1] & 0xFF)));
				}
			}
		} else if ((atr[0] & 0xCF) == 0x82) {
			this.put("interfaceBytes", this, atr);
		} else {
			GPError.throwAsGPErrorEx(this, clazzName, GPError.INVALID_DATA, 0,
					"Argument is not an ATR");
		}
	}


	/**
	 * Pad string to the left
	 */
	private static String c5(int v) {
		String r = "" + v;
		if (r.length() >= 5) {
			return r;
		} else {
			return "     ".substring(r.length()) + r;
		}
	}

	/**
	 * Dump buffer in hexadecimal format with character codes
	 * 
	 * @param data
	 *            Byte buffer
	 * @param offset
	 *            Offset into byte buffer
	 * @param length
	 *            Length of data to be dumped
	 * @return String containing the dump
	 */
	private static String mydump(byte[] data, int offset, int length) {
		StringBuffer buffer = new StringBuffer(80);
		int i, ofs;
		char ch;

		ofs = offset;

		for (i = 0; i < length; i++, ofs++) {
			buffer.append(HexUtil.toHexXX(data[ofs]));
		}

		buffer.append(' ');
		ofs = offset;

		for (i = 0; i < length; i++, ofs++) {
			ch = (char) (data[ofs] & 0xFF);
			if ((ch < 32) || ((ch >= 127) && (ch <= 0xA0)))
				ch = '.';
			buffer.append(ch);
		}
		return buffer.toString();
	}

	/**
	 * Decode ATR into string
	 * 
	 */
	@Override
	@JSFunction
	public String toString() {

		StringBuffer r = new StringBuffer(100);
		r.append(HexUtil.toHexString(atr)).append('\n');
		int i = 0;
		int hb = 0;
		int td = 0;

		if ((atr[i] == 0x3B) || (atr[i] == 0x3F)) {
			if (atr[i] == 0x3B)
				r.append("TS  : 3B  Direct logic\n");
			else
				r.append("TS  : " + HexUtil.toHexXX(atr[i])
						+ "  Inverse logic\n");

			i++;
			hb = atr[i] & 0x0F;
			td = atr[i] & 0xF0;
			r.append("TO  : " + HexUtil.toHexXX(atr[i]) + "  K    = " + c5(hb)
					+ " byte [historical characters]\n");
			i++;

			if ((td & 0x10) == 0x10) {
				int f = (atr[i] >> 4) & 0x0F;
				int d = atr[i] & 0x0F;

				r.append("TA1 : "
						+ HexUtil.toHexXX(atr[i])
						+ "  Fi/f =  "
						+ fi[f]
						+ "   [clock rate conversion factor / max. frequency (MHz)]\n");
				r.append("          Di   =   " + di[d]
						+ "      [bit rate conversion factor]\n");
				i++;
			}

			if ((td & 0x20) == 0x20) {
				r.append("TB1 : " + HexUtil.toHexXX(atr[i]) + "  pa   = "
						+ c5((atr[i] & 0x80) == 0x80 ? 2 : 4)
						+ " %    [programming voltage accurancy]\n");
				r.append("          I    = " + c5(curr[(atr[i] & 0x60) >> 5])
						+ " mA   [maximum current]\n");
				r.append("          P    = " + c5(atr[i] & 0x0F)
						+ " V    [programming voltage]\n");
				i++;
			}

			if ((td & 0x40) == 0x40) {
				r.append("TC1 : " + HexUtil.toHexXX(atr[i]) + "  N    = "
						+ c5(atr[i] & 0xFF) + " etu  [extra guardtime]\n");
				i++;
			}

			int j = 2;
			while ((td & 0x80) == 0x80) {
				r.append("TD" + (j - 1) + " : " + HexUtil.toHexXX(atr[i])
						+ "  T    =   " + prot[atr[i] & 0x0F]
						+ "      [protocol type]\n");

				td = atr[i];
				i++;

				if ((td & 0x0F) == 0x0F) { // Global Interface Bytes
					if ((td & 0x10) == 0x10) {
						r.append("TA" + j + " : " + HexUtil.toHexXX(atr[i])
								+ "  X    = " + xi[(atr[i] & 0xC0) >> 6]
								+ "    [clock stop indicator]\n");
						r.append("          U    = " + ui[atr[i] & 0x07]
								+ "    [class indicator (5, 3, 1.8 V)]\n");
						i++;
					}

					if ((td & 0x20) == 0x20) {
						r.append("TB" + j + " : " + HexUtil.toHexXX(atr[i])
								+ "\n");
						i++;
					}

					if ((td & 0x40) == 0x40) {
						r.append("TC" + j + " : " + HexUtil.toHexXX(atr[i])
								+ "\n");
						i++;
					}
				} else if ((j > 2) && (td & 0x0F) == 1) { // T=1 Interface Bytes
					if ((td & 0x10) == 0x10) {
						r.append("TA" + j + " : " + HexUtil.toHexXX(atr[i])
								+ "  IFSC = "
								+ c5(atr[i] == 0 ? 256 : (atr[i] & 0xFF))
								+ "      [information field size]\n");
						i++;
					}

					if ((td & 0x20) == 0x20) {
						r.append("TB" + j + " : " + HexUtil.toHexXX(atr[i])
								+ "  CWT  = " + c5((1 << (atr[i] & 0x0F)) + 11)
								+ " etu  [character waiting time]\n");
						int bwt = (atr[i] & 0xF0) >> 4;
						if (bwt < 10) {
							r.append("          BWT  = "
									+ c5((1 << (bwt)) * 960 + 11)
									+ " etu  [block waiting time]\n");
						} else {
							String bwtch = "ABCDEF"
									.substring(bwt - 10, bwt - 9);
							r.append("          BWT  =     " + bwtch
									+ "      [block waiting time code]\n");
						}
						i++;
					}

					if ((td & 0x40) == 0x40) {
						r.append("TC" + j + " : " + HexUtil.toHexXX(atr[i])
								+ "  EDC  = "
								+ ((atr[i] & 1) == 1 ? "CRC" : "LRC")
								+ "        [error detection code]\n");
						i++;
					}
				} else { // All other Interface Bytes
					if ((td & 0x10) == 0x10) {
						if (j == 2) {
							r.append("TA"
									+ j
									+ " : "
									+ HexUtil.toHexXX(atr[i])
									+ "  S/N  =   "
									+ (((atr[i] & 0x80) == 0x80) ? "yes"
											: "no ")
									+ "      [specific mode]\n");
							r.append("          DP   =   "
									+ (((atr[i] & 0x80) == 0x80) ? "implicit"
											: "IF-bytes")
									+ " [protocol parameter]\n");
							r.append("          T    =   "
									+ prot[atr[i] & 0x0F]
									+ "      [protocol type]\n");
						} else {
							r.append("TA" + j + " : " + HexUtil.toHexXX(atr[i])
									+ "  BS   = "
									+ c5(atr[i] == 0 ? 256 : (atr[i] & 0xFF))
									+ "      [block size]\n");
						}
						i++;
					}

					if ((td & 0x20) == 0x20) {
						if (j == 2) {
							r.append("TB" + j + " : " + HexUtil.toHexXX(atr[i])
									+ "  P2   =  "
									+ ((atr[i] & 0xFF) / 10.0)
									+ " Volt [programming voltage]\n");
						} else {
							r.append("TB" + j + " : " + HexUtil.toHexXX(atr[i])
									+ "\n");
						}
						i++;
					}

					if ((td & 0x40) == 0x40) {
						if (j == 2) {
							r.append("TC"
									+ j
									+ " : "
									+ HexUtil.toHexXX(atr[i])
									+ "  W    = "
									+ c5(atr[i] & 0xFF)
									+ "      [waiting time adjustment factor]\n");
						} else {
							r.append("TC" + j + " : " + HexUtil.toHexXX(atr[i])
									+ "\n");
						}
						i++;
					}
				}
				j++;
			}
		} else if ((atr[i] & 0xCF) == 0x82) {
			int j = (atr[i] & 0xF0) >> 4;
			r.append("H1  : " + HexUtil.toHexXX(atr[i]) + "  S = " + j + ", "
					+ sprot[j - 8] + "\n");

			i++;
			j = 64 << ((atr[i] & 0x78) >> 3);
			if (j == 64)
				j = 0;

			r.append("H2  : " + HexUtil.toHexXX(atr[i]) + "  Units = " + j
					+ "\n");
			r.append("          Unit Size = " + (1 << (atr[i] & 0x07))
					+ " bits\n");

			i++;
			r.append("H3  : " + HexUtil.toHexXX(atr[i]) + "  Category = "
					+ (atr[i] == 0x10 ? "Synchronous card" : "Unknown") + "\n");

			i++;
			if ((atr[i] & 0x80) == 0x80)
				r.append("H4  : " + HexUtil.toHexXX(atr[i])
						+ "  Dir Reference = " + (atr[i] & 0xFF) + "\n");
			else
				r.append("H4  : " + HexUtil.toHexXX(atr[i]) + "\n");
		}

		if (hb > 0) {
			td = atr[i] & 0xFF;

			if ((td == 0x00) || (td == 0x80)) {
				hb -= (td == 0x00 ? 4 : 1);
				i++;

				while (hb > 0) {
					int tag = atr[i] & 0xF0;
					int len = atr[i] & 0x0F;

					if (len > hb - 1) {
						r.append("Error: Compact TLV length field exceeds historical bytes - truncated\n");
						len = hb - 1;
					}

					i++;
					hb -= len + 1;

					switch (tag) {
					case 0x10:
						r.append("Country indicator      : "
								+ mydump(atr, i, len) + "\n");
						break;
					case 0x20:
						r.append("Issuer indicator       : "
								+ mydump(atr, i, len) + "\n");
						break;
					case 0x30:
						r.append("Card service indicator :\n");
						if ((atr[i] & 0x80) == 0x80) {
							r.append("    Application selection by full DF name\n");
						}
						if ((atr[i] & 0x40) == 0x40) {
							r.append("    Application selection by partial DF name\n");
						}
						if ((atr[i] & 0x20) == 0x20) {
							r.append("    BER-TLV objects in EF.DIR\n");
						}
						if ((atr[i] & 0x10) == 0x10) {
							r.append("    BER-TLV objects in EF.ATR\n");
						}
						switch (atr[i] & 0x0E) {
						case 0x00:
							r.append("    EF.DIR / EF.ATR access with READ RECORD\n");
							break;
						case 0x04:
							r.append("    EF.DIR / EF.ATR access with GET DATA\n");
							break;
						case 0x08:
							r.append("    EF.DIR / EF.ATR access with READ BINARY\n");
							break;
						}
						if ((atr[i] & 0x01) == 0x01) {
							r.append("    Card without MF\n");
						} else {
							r.append("    Card with MF\n");
						}
						break;
					case 0x40:
						r.append("Initial access data    : "
								+ mydump(atr, i, len) + "\n");
						break;
					case 0x50:
						r.append("Card issuer data       : "
								+ mydump(atr, i, len) + "\n");
						break;
					case 0x60:
						r.append("Pre-issuing data       : "
								+ mydump(atr, i, len) + "\n");
						break;
					case 0x70:
						r.append("Card capabilities      :\n");
						if (len > 0) {
							if ((atr[i] & 0x80) == 0x80) {
								r.append("    DF selection by full DF name\n");
							}
							if ((atr[i] & 0x40) == 0x40) {
								r.append("    DF selection by partial DF name\n");
							}
							if ((atr[i] & 0x20) == 0x20) {
								r.append("    DF selection by path DF name\n");
							}
							if ((atr[i] & 0x10) == 0x10) {
								r.append("    DF selection by file identifier\n");
							}
							if ((atr[i] & 0x08) == 0x08) {
								r.append("    Implicit DF selection\n");
							}
							if ((atr[i] & 0x04) == 0x04) {
								r.append("    Short EF identifier supported\n");
							}
							if ((atr[i] & 0x02) == 0x02) {
								r.append("    Record number supported\n");
							}
							if ((atr[i] & 0x01) == 0x01) {
								r.append("    Record identifier supported\n");
							}
						}

						len--;
						i++;

						if (len > 0) {
							if ((atr[i] & 0x80) == 0x80) {
								r.append("    EFs of TLV structure supported\n");
							}
							switch (atr[i] & 0x60) {
							case 0x00:
								r.append("    One-time write\n");
								break;
							case 0x20:
								r.append("    Proprietary write\n");
								break;
							case 0x40:
								r.append("    Write OR\n");
								break;
							case 0x60:
								r.append("    Write AND\n");
								break;
							}
							int dusize = (4 << (atr[i] & 0x0F));
							r.append("    Data unit size is " + dusize
									+ " bits\n");

							if ((atr[i] & 0x10) == 0x10) {
								r.append("    'FF' means long private tags with constructed encoding TLV object\n");
							} else {
								r.append("    'FF' means padding in BER.TLV coded data fields\n");
							}
						}

						len--;
						i++;

						if (len > 0) {
							if ((atr[i] & 0x80) == 0x80) {
								r.append("    Command chaining supported\n");
							}
							if ((atr[i] & 0x40) == 0x40) {
								r.append("    Extended Le and Lc fields supported\n");
							}
							if ((atr[i] & 0x10) == 0x10) {
								r.append("    Logical channel number assigned by the card\n");
							}
							if ((atr[i] & 0x08) == 0x08) {
								r.append("    Logical channel number assigned by the interface device\n");
							}
							if ((atr[i] & 0x18) != 0x00) {
								int maxlc = ((atr[i] & 0x20) >> 2)
										+ (atr[i] & 0x3) + 1;
								r.append("    Maximum number of logical channels is "
										+ maxlc + "\n");
							}
						}
						break;
					case 0x80:
						r.append("Status byte(s)         : "
								+ mydump(atr, i, len) + "\n");
						break;
					case 0xF0:
						r.append("Application identifier : "
								+ mydump(atr, i, len) + "\n");
						break;
					}
					i += len;
				}
				if (td == 0x00) {
					r.append("Status bytes : " + HexUtil.toHexXX(atr[i])
							+ HexUtil.toHexXX(atr[i + 1])
							+ HexUtil.toHexXX(atr[i + 2]));
				}
			} else {
				r.append(mydump(atr, i, hb) + "\n");
			}
		}

		return r.toString();
	}

	@Override
	public String getClassName() {
		return clazzName;
	}
}
